package com.dx.financehy.modules.security.controller;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.dx.financehy.annotation.rest.AnonymousPostMapping;
import com.dx.financehy.config.security.MyUsernamePasswordAuthenticationToken;
import com.dx.financehy.exception.BadRequestException;
import com.dx.financehy.modules.security.config.bean.LoginCodeEnum;
import com.dx.financehy.modules.security.config.bean.LoginProperties;
import com.dx.financehy.modules.security.config.bean.SecurityProperties;
import com.dx.financehy.modules.security.domain.dto.AuthUserDto;
import com.dx.financehy.modules.security.domain.dto.JwtUserDto;
import com.dx.financehy.modules.security.entity.dto.WxAuthDTO;
import com.dx.financehy.modules.security.security.TokenProvider;
import com.dx.financehy.modules.security.service.AuthService;
import com.dx.financehy.modules.security.service.OnlineUserService;
import com.dx.financehy.modules.security.utils.LoginUtil;
import com.dx.financehy.modules.system.domain.User;
import com.dx.financehy.modules.system.domain.dto.AddUserDTO;
import com.dx.financehy.modules.system.domain.dto.UserLoginDto;
import com.dx.financehy.modules.system.mapper.UserMapper;
import com.dx.financehy.modules.system.service.UserService;
import com.dx.financehy.utils.R;
import com.dx.financehy.utils.RedisUtils;
import com.dx.financehy.utils.RequestHolder;
import com.dx.financehy.utils.StringUtils;
import com.dx.financehy.utils.enums.LoginMode;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.wf.captcha.base.Captcha;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.beans.BeanUtils;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <pre>
 * @描述：授权、根据token获取用户详细信息
 * @作者：CLD
 * @创建时间：2022-06-10 14:49
 * </pre>
 **/
@Slf4j
@RestController
@RequestMapping("/security/auth")
@RequiredArgsConstructor
@Api(tags = "系统授权管理")
public class AuthorizationController {

    private final SecurityProperties properties;
    private final RedisUtils redisUtils;
    private final OnlineUserService onlineUserService;
    private final TokenProvider tokenProvider;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;
    private final LoginProperties loginProperties;

    private final AuthService authService;
    private final LoginUtil loginUtil;

    private final UserService userService;

    private final UserMapper userMapper;

    @ApiOperation("微信登录授权")
    @AnonymousPostMapping(value = "/wxOpenIdLogin")
    public R<Map<String, Object>> wxOpenIdLogin(@Validated @RequestBody WxAuthDTO wxAuthDTO) {
        User user;
        try {
            user = authService.wxOpenIdLogin(wxAuthDTO);
        } catch (WxErrorException e) {
            log.error("微信登录失败", e);
            throw new BadRequestException("微信登录失败!");
        }
        if (user == null) {
            log.warn("微信登录，用户不存在");
            throw new BadRequestException("用户不存在");
        }
        Boolean enabled = user.getEnabled();
        if (!enabled) {
            log.warn("微信登录，账号不可用");
            throw new BadRequestException("账号不可用");
        }
        Boolean isLogin = user.getIsLogin();
        if (!isLogin) {
            log.warn("微信登录，账号禁止登录");
            throw new BadRequestException("账号账号禁止登录");
        }
        Map<String, Object> authInfo = authActionOpenId(user.getUsername(), user.getPassword(), LoginMode.OTHER,user);
        return R.ok(authInfo);
    }

    @ApiOperationSupport(author = "itHuang")
    @ApiOperation("微信手机号登录授权")
    @AnonymousPostMapping(value = "/wxTextUseLogin")
    public R<Object> login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
        User user;
        user = userService.getByUsername(authUser.getUsername());

        if (user == null) {
            AddUserDTO req = new AddUserDTO();
            BeanUtils.copyProperties(authUser,req);
            req.setUserType(3);
            if (StringUtils.isNotBlank(user.getMerchantId())){
                req.setMerchantId(authUser.getMerchantId());
            }
            user = userService.addLoginSysUser(req);
        }else {
            if (StringUtils.isBlank(user.getMerchantId()) && StringUtils.isNotBlank(authUser.getMerchantId())){
                LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
                updateWrapper.eq(User::getId,user.getId())
                        .set(User::getMerchantId,authUser.getMerchantId());
                userMapper.update(null,updateWrapper);
            }

            Boolean enabled = user.getEnabled();
            if (!enabled) {
                log.warn("微信登录，账号不可用");
                throw new BadRequestException("账号不可用");
            }
            Boolean isLogin = user.getIsLogin();
            if (!isLogin) {
                log.warn("微信登录，账号禁止登录");
                throw new BadRequestException("账号账号禁止登录");
            }
        }

        Map<String, Object> authInfo = authAction(user.getUsername(), user.getPassword(), LoginMode.OTHER);
        return R.ok(authInfo);
    }

    @ApiOperation("微信登录授权")
    @AnonymousPostMapping(value = "/wxlogin")
    public R<Map<String, Object>> wxlogin(@Validated @RequestBody WxAuthDTO wxAuthDTO) {
        User user;
        try {
            user = authService.wxlogin(wxAuthDTO);
        } catch (WxErrorException e) {
            log.error("微信登录失败", e);
            throw new BadRequestException("微信登录失败!");
        }
        if (user == null) {
            log.warn("微信登录，用户不存在");
            throw new BadRequestException("用户不存在");
        }
        Boolean enabled = user.getEnabled();
        if (!enabled) {
            log.warn("微信登录，账号不可用");
            throw new BadRequestException("账号不可用");
        }
        Boolean isLogin = user.getIsLogin();
        if (!isLogin) {
            log.warn("微信登录，账号禁止登录");
            throw new BadRequestException("账号账号禁止登录");
        }
        Map<String, Object> authInfo = authAction(user.getUsername(), user.getPassword(), LoginMode.OTHER);
        return R.ok(authInfo);
    }

    @ApiOperation("微信免密登录授权")
    @AnonymousPostMapping(value = "/weChatlogin")
    public R<Map<String, Object>> weChatlogin(@Validated @RequestBody WxAuthDTO wxAuthDTO) {
        User user;
        try {
            user = authService.weChatlogin(wxAuthDTO);
        } catch (WxErrorException e) {
            log.error("微信登录失败", e);
            throw new BadRequestException("微信登录失败!");
        }
        if (user == null) {
            log.warn("微信登录，用户不存在");
            throw new BadRequestException("用户不存在");
        }
        Boolean enabled = user.getEnabled();
        if (!enabled) {
            log.warn("微信登录，账号不可用");
            throw new BadRequestException("账号不可用");
        }
        Map<String, Object> authInfo = authAction(user.getUsername(), user.getPassword(), LoginMode.OTHER);
        return R.ok(authInfo);
    }

    /**
     * 开始认证
     *
     * @param username
     * @param password
     * @return
     */
    private Map<String, Object> authActionOpenId(String username, String password, LoginMode loginMode,User user) {
        long expire = redisUtils.getExpire(loginUtil.loginFailedLockKey(username));
        if (expire > 0) {
            throw new BadRequestException("账号已被锁定");
        }
        Authentication authentication;
        try {
            MyUsernamePasswordAuthenticationToken authenticationToken
                    = new MyUsernamePasswordAuthenticationToken(username, password, loginMode);
            authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (AuthenticationException e) {
            loginUtil.incrementCount(username);
            log.error("用户名或密码错误!", e);
            throw new BadRequestException("用户名或密码错误");
        }
        // 生成令牌
        String token = tokenProvider.createToken(authentication);
        final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
        // 保存在线信息
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        onlineUserService.save(jwtUserDto, token, request);
        // 返回 token 与 用户信息
        /*UserLoginDto user = jwtUserDto.getUser();
        user.setPassword(null);
        user.setEnabled(null);*/
        Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
            put("token", properties.getTokenStartWith() + token);
            put("user", user);
        }};
        if (loginProperties.isSingleLogin()) {
            //踢掉之前已经登录的token
            onlineUserService.checkLoginOnUser(username, token);
        }
        return authInfo;
    }

    /**
     * 开始认证
     *
     * @param username
     * @param password
     * @return
     */
    private Map<String, Object> authAction(String username, String password, LoginMode loginMode) {
        long expire = redisUtils.getExpire(loginUtil.loginFailedLockKey(username));
        if (expire > 0) {
            throw new BadRequestException("账号已被锁定");
        }
        Authentication authentication;
        try {
            MyUsernamePasswordAuthenticationToken authenticationToken
                    = new MyUsernamePasswordAuthenticationToken(username, password, loginMode);
            authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (AuthenticationException e) {
            loginUtil.incrementCount(username);
            log.error("用户名或密码错误!", e);
            throw new BadRequestException("用户名或密码错误");
        }
        // 生成令牌
        String token = tokenProvider.createToken(authentication);
        final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
        // 保存在线信息
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        onlineUserService.save(jwtUserDto, token, request);
        // 返回 token 与 用户信息
        UserLoginDto user = jwtUserDto.getUser();
        user.setPassword(null);
        user.setEnabled(null);
        Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
            put("token", properties.getTokenStartWith() + token);
            put("user", user);
        }};
        if (loginProperties.isSingleLogin()) {
            //踢掉之前已经登录的token
            onlineUserService.checkLoginOnUser(username, token);
        }
        return authInfo;
    }

    @ApiOperation("获取验证码")
    @AnonymousPostMapping(value = "/code")
    public R<Object> getCode() {
        if (!loginProperties.getLoginCode().getEnabled()) {
            // 验证码信息
            Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
                put("enabled", 0);
            }};
            return R.ok(imgResult);
        }
        // 获取运算的结果
        Captcha captcha = loginProperties.getCaptcha();
        String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
        //当验证码类型为 arithmetic时且长度 >= 2 时，captcha.text()的结果有几率为浮点型
        String captchaValue = captcha.text();
        if (captcha.getCharType() - 1 == LoginCodeEnum.arithmetic.ordinal() && captchaValue.contains(".")) {
            captchaValue = captchaValue.split("\\.")[0];
        }
        // 保存
        redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
        // 验证码信息
        Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
            put("enabled", 1);
            put("img", captcha.toBase64());
            put("uuid", uuid);
        }};
        return R.ok(imgResult);
    }

    @ApiOperation("退出登录")
    @AnonymousPostMapping(value = "/logout")
    public R<Object> logout(HttpServletRequest request) {
        onlineUserService.logout(tokenProvider.getToken(request));
        return R.ok();
    }

}
